<?php
/***
 * Candy框架 文件夹类
 * 
 * $Author: 刘森 (fingerboy@qq.com) $
 * $Date: 2020-04-12 13:45:33 $   
 */
 
namespace Candy\Extend;

final Class Dir {

    private static $_values = [];
    public static $error = "";

    /**
     * 生成目录
	 *
     * @param  string  $path 目录
     * @param  integer $mode 权限
     * @return boolean
     */
    public static function create(string $path,int $mode = 0755): bool
	{
		if(is_dir($path)) return true;
		$path = str_replace("\\", "/", $path);
		if(substr($path, -1) != '/') $path = $path.'/';
		$temp = explode('/', $path);
		$cur_dir = '';
		$max = count($temp) - 1;
		for($i=0; $i<$max; $i++) {
			$cur_dir .= $temp[$i].'/';
			if (@is_dir($cur_dir)) continue;
			@mkdir($cur_dir, $mode, true);
			@chmod($cur_dir, $mode);
		}
		return is_dir($path);
    }

    /**
     * 取得目录和子目录下面的文件信息
	 *
     * @param mixed $pathname 路径
     */
    public static function listFile(string $pathname,string $pattern = '*',array $protect = []): array
	{
        static $_listDirs = array();
        $guid = md5($pathname . $pattern);
        if (!isset($_listDirs[$guid])) {
            $dir = array();
            $list = glob($pathname . $pattern);
			
			//指定类型时添加上目录
			if($pattern != '*'){
				$dirList = glob($pathname . '*', GLOB_ONLYDIR);
				$list = array_merge($list, $dirList);
			}
            foreach ($list as $file) {
				$dir[] = [
					'filename'=>preg_replace('/^.+[\\\\\\/]/', '', $file),
					'pathname'=>realpath($file),
					'owner'=>fileowner($file),
					'perms'=>fileperms($file),
					'inode'=>fileinode($file),
					'group'=>filegroup($file),
					'path'=>dirname($file),
					'atime'=>fileatime($file),
					'ctime'=>filectime($file),
					'size'=>filesize($file),
					'type'=>filetype($file),
					'ext'=>is_file($file) ? strtolower(substr(strrchr(basename($file), '.'), 1)) : '',
					'mtime'=>filemtime($file),
					'isDir'=>is_dir($file),
					'isFile'=>is_file($file),
					'isLink'=>is_link($file),
					'isReadable'=>is_readable($file),
					'isWritable'=>is_writable($file)
				];
				
				if(is_dir($file) && !in_array($file, $protect)){
					$sonList = self::listFile(realpath($file) . '/', $pattern, $protect);
					$dir = array_merge($dir, $sonList);
				}
            }
            // 对结果排序 保证目录在前面
            usort($dir, function ($a,$b){
				$k  =  "isDir";
				if($a[$k]  ==  $b[$k])  return  0;
				return  $a[$k]>$b[$k]?-1:1;
			});
            self::$_values = $dir;
            $_listDirs[$guid] = $dir;
        } else {
            self::$_values = $_listDirs[$guid];
        }
		
		return self::$_values ?? [];
    }

    /**
     * 返回数组中的当前元素（单元）
	 *
     * @return array
     */
    public static function current(array $arr): mixed
	{
        if (!is_array($arr)) {
            return false;
        }
        return current($arr);
    }

    /**
     * 文件上次访问时间
	 *
     * @return integer
     */
    public function getATime(): int
	{
        $current = $this->current($this->_values);
        return $current['atime'];
    }

    /**
     * 取得文件的 inode 修改时间
	 *
     * @return integer
     */
    public function getCTime(): int
	{
        $current = $this->current($this->_values);
        return $current['ctime'];
    }

    /**
     * 遍历子目录文件信息
	 *
     * @return DirectoryIterator
     */
    public function getChildren()
	{
        $current = $this->current($this->_values);
        if ($current['isDir']) {
            return new Dir($current['pathname']);
        }
        return false;
    }

    /**
     * 取得文件名
	 *
     * @return string
     */
    public function getFilename(): string
	{
        $current = $this->current($this->_values);
        return $current['filename'];
    }

    /**
     * 取得文件的组
	 *
     * @return integer
     */
    public function getGroup(): int
	{
        $current = $this->current($this->_values);
        return $current['group'];
    }

    /**
     * 取得文件的 inode
	 *
     * @return integer
     */
    public function getInode(): int
	{
        $current = $this->current($this->_values);
        return $current['inode'];
    }

    /**
     * 取得文件的上次修改时间
	 *
     * @return integer
     */
    public function getMTime(): int
	{
        $current = $this->current($this->_values);
        return $current['mtime'];
    }

    /**
     * 取得文件的所有者
	 *
     * @return string
     */
    function getOwner(): string
	{
        $current = $this->current($this->_values);
        return $current['owner'];
    }

    /**
     * 取得文件路径，不包括文件名
	 *
     * @return string
     */
    public function getPath(): string
	{
        $current = $this->current($this->_values);
        return $current['path'];
    }

    /**
     * 取得文件的完整路径，包括文件名
	 *
     * @return string
     */
    public function getPathname(): string
	{
        $current = $this->current($this->_values);
        return $current['pathname'];
    }

    /**
     * 取得文件的权限
	 *
     * @return integer
     */
    public function getPerms(): int
	{
        $current = $this->current($this->_values);
        return $current['perms'];
    }

    /**
     * 取得文件的大小
	 *
     * @return integer
     */
    public function getSize(): int
	{
        $current = $this->current($this->_values);
        return $current['size'];
    }

    /**
     * 取得文件类型
	 *
     * @return string
     */
    public function getType(): int
	{
        $current = $this->current($this->_values);
        return $current['type'];
    }

    /**
     * 是否为目录
	 *
     * @return boolen
     */
    public function isDir(): bool
	{
        $current = $this->current($this->_values);
        return $current['isDir'];
    }

    /**
     * 是否为文件
	 *
     * @return boolen
     */
    public function isFile(): bool
	{
        $current = $this->current($this->_values);
        return $current['isFile'];
    }

    /**
     * 文件是否为一个符号连接
	 *
     * @return boolen
     */
    public function isLink(): bool
	{
        $current = $this->current($this->_values);
        return $current['isLink'];
    }

    /**
     * 文件是否可以执行
	 *
     * @return boolen
     */
    public function isExecutable(): bool
	{
        $current = $this->current($this->_values);
        return $current['isExecutable'];
    }

    /**
     * 文件是否可读
	 *
     * @return boolen
     */
    public function isReadable(): bool
	{
        $current = $this->current($this->_values);
        return $current['isReadable'];
    }

    /**
     * 获取foreach的遍历方式
	 *
     * @return string
     */
    public function getIterator(): string
	{
        return new ArrayObject($this->_values);
    }
	
	/**
     * 返回目录的数组信息
	 *
     * @return array
     */
    public function toArray(): array
	{
        return $this->_values;
    }
	
    /**
     * 判断目录是否为空
	 *
     * @return boolen
     */
    public function isEmpty(string $directory): bool
	{
        $handle = opendir($directory);
        while (($file = readdir($handle)) !== false) {
            if ($file != "." && $file != "..") {
                closedir($handle);
                return false;
            }
        }
        closedir($handle);
        return true;
    }

    /**
     * 取得目录中的文件列表
	 *
     * @return array
     */
    public static function getFiles($directory,string $pattern = '*', string $type = 'name',string $sort = 'DESC'): array
	{
		static $_listFiles = array();
		//保护
		$protect = $full = false;
		if(is_array($directory)){
			[$directory, $protect, $full] = $directory;
		}
		$protectFiles = isset($protect['files']) ? $protect['files'] : [];
		$protectDirs = isset($protect['dirs']) ? $protect['dirs'] : [];
		
		
		$guid = md5($directory . $pattern . $type . $sort);
        if (!isset($_listFiles[$guid])) {
			$list = self::listFile($directory, $pattern, $protectDirs);
			$files = [];
			foreach($list as $file){
				if($file['isFile'] && !in_array($file['filename'], $protectFiles)){
					$name[] = $file['filename'];
					$size[] = $file['size'];
					$time[] = $file['atime'];//获取文件最近修改日期
					$files[] = $full ? ['name'=>$file['filename'],'size'=>$file['size'],'time'=>$file['atime'],'path'=>$file['pathname']] : $file['pathname'];
				}
			}
			$sort == 'DESC' ? array_multisort($$type, SORT_DESC, SORT_STRING, $files) : array_multisort($$type, SORT_ASC, SORT_STRING, $files);
			$_listFiles[$guid] = $files;
		}
		
		return $_listFiles[$guid];
    }
	
    /**
     * 取得目录中的结构信息
	 *
     * @return array
     */
    public static function getList(string $directory): array
	{
        $scandir = scandir($directory);
        $dir = [];
        foreach ($scandir as $k => $v) {
            if ($v == '.' || $v == '..') {
                continue;
            }
            $dir[] = $v;
        }
        return $dir;
    }

    /**
     * 删除目录（包括下面的文件）
	 *
     * @return void
     */
    public static function delDir(string $directory,bool $subdir = true)
	{
        if (is_dir($directory) == false) {
            return false;
        }
        $handle = opendir($directory);
        while (($file = readdir($handle)) !== false) {
            if ($file != "." && $file != "..") {
                is_dir("$directory/$file") ? Dir::delDir("$directory/$file") : @unlink("$directory/$file");
            }
        }
        if (readdir($handle) == false) {
            closedir($handle);
            @rmdir($directory);
        }
    }

    /**
     * 删除目录下面的所有文件，但不删除目录
	 *
     * @return void
     */
    public static function del(string $directory)
	{
        if (is_dir($directory) == false) {
            return false;
        }
        $handle = opendir($directory);
        while (($file = readdir($handle)) !== false) {
            if ($file != "." && $file != ".." && is_file("$directory/$file")) {
                @unlink("$directory/$file");
            }
        }
        closedir($handle);
    }

    /**
     * 复制目录
	 *
     * @return void
     */
    public static function copyDir(string $source,string $destination)
	{
        if (is_dir($source) == false) {
            return false;
        }
        if (is_dir($destination) == false) {
            mkdir($destination, 0755, true);
        }
        $handle = opendir($source);
        while (false !== ($file = readdir($handle))) {
            if ($file != "." && $file != "..") {
                if (is_dir("$source/$file")) {
                    self::copyDir("$source/$file", "$destination/$file");
                } else {
                    copy("$source/$file", "$destination/$file");
                }
            }
        }
        closedir($handle);
    }
	
    /**
     * 移动目录
	 *
     * @return void
     */
    public static function moveDir(string $source,string $destination)
	{
        if (is_dir($source) == false) {
            return false;
        }
        if (is_dir($destination) == false) {
            mkdir($destination, 0755, true);
        }
        $handle = opendir($source);
        while (false !== ($file = readdir($handle))) {
            if ($file != "." && $file != "..") {
                if (is_dir("$source/$file")) {
                    self::moveDir("$source/$file", "$destination/$file");
					@rmdir("$source/$file");
                } else {
                    copy("$source/$file", "$destination/$file");
					@unlink("$source/$file");
                }
            }
        }
        closedir($handle);
    }
	
    /**
     * 目录详情
	 *
     * @param  string  $path 目录
     * @return array
     */
    public static function state($path): array
	{
		if(is_string($path)) $path = [$path];
		
		clearstatcache();
		$info = [];
		foreach ($path as $key => $val){
			$tempArray = [];
			if(file_exists($val)){
				$tempArray['R'] = is_readable($val) ? 'Y' : 'N';
				$tempArray['W'] = is_writable($val) ? 'Y' : 'N';
				if(is_dir($val)) $tempArray['Ex'] = is_executable($val) ? 'Y' : 'N';
			}
			else{
				$tempArray = [];
			}
			$info[$val] = $tempArray;
		}
		return $info;
    }
	
    /**
     * 目录大小
	 *
     * @param  string  $path 目录
     * @param  string  $dept 层数
     * @return array
     */
    public static function size(string $path,int $dept = 0): string
	{
		$dirSize=0;
		if(file_exists($path) && $dirHandle=@opendir($path)){
			while($fileName = readdir($dirHandle)){
				if($fileName != '.' && $fileName != '..'){
					$subFile=$path .'/'. $fileName;
					if(is_dir($subFile))
						$dirSize += self::size($subFile, $dept+1);
					if(is_file($subFile))
						$dirSize += filesize($subFile);
				}
			}
			closedir($dirHandle);
			if($dept == 0){
				return Utils::sizeCount($dirSize);
			}else{
				return $dirSize;
			}
		}
    }
}
